#ifndef Liste_h
#define Liste_h

#include <vector>
#include <string>

#include "Element.h"
#include "IteratorListe.h" 

using namespace std;

class InsertionException {

public:
	InsertionException(string error) {
		this->errorMsg = error;
	}
	virtual ~InsertionException() {

	}
	virtual string what() const throw() {
		return errorMsg;
	}
private:
	string errorMsg;
};


class AccessException {

public:
	AccessException(string error) {
		this->errorMsg = error;
	}
	virtual ~AccessException() {

	}
	virtual string what() const throw() {
		return errorMsg;
	}
private:
	string errorMsg;
};

class RemovalException {

public:
	RemovalException(string error) {
		this->errorMsg = error;
	}
	virtual ~RemovalException() {

	}
	virtual string what() const throw() {
		return errorMsg;
	}
private:
	string errorMsg;
};


template <class T> class Liste {
	
private:
	T *tete;
	T *dernier;
public:
	
	typedef IteratorListe iterator;
		
	Liste() {
		this->tete = NULL;
		this->dernier = NULL;
	}

	~Liste() {
		T *courant = this->tete;
		T *suivant = NULL;
		while (courant != NULL) {
			suivant = dynamic_cast<T *>(courant->suivant);
			delete courant;
			courant = suivant;
		}
	}

	string rapport() {
		string s = "";
		T *courant = this->tete;
		while (courant != NULL) {
			s += courant->rapport();
			courant = courant->suivant;
		}
		return s;
	}

	void retireElement(int i) {
		if (this == NULL) throw RemovalException("retireElement Unable to remove the element");
		T *toRemove = NULL;
		try {
			toRemove = this->operator [](i);
		} catch (AccessException e) {
			throw RemovalException("retireElement  Unable to remove the element because of an incorrect index");
		}
		if (i == 0) {
			if (this->tete == this->dernier) {
				this->tete = NULL;
				this->dernier = NULL;
			} else {
				this->tete = toRemove->suivant;
			}
		} else if (toRemove->suivant == NULL) {
			T *previous = NULL;
			try {
				previous = this->operator [](i-1);
				this->dernier = previous;
				previous->suivant = NULL;
			} catch (AccessException e) {
				throw RemovalException("retireElement Something went terribly wrong somewhere.... ");
			}
		} else {
			T *previous = NULL;
			try {
				previous = this->operator [](i-1);
				previous->suivant = toRemove->suivant;
				
			} catch (AccessException e) {
				throw RemovalException("retireElement Something went terribly wrong somewhere.... ");
			}
		}
		delete toRemove;
	}

	void retireElement(T *element) {
		T *courant = this->tete;
		int j = 0;
		while (courant != element) {
			courant = courant->suivant;
			j++;
		}
		this->retireElement(j);	
	}
	
	// TODO appeler le constructeur de copie pour les elements de la liste
	/*
	void ajouterListe(Liste<T> liste) {
		T *courant = liste->tete;
		while (courant != NULL) {
			Element element = new Element();
			
			element->
			this->dernier = 
			courant = courant->suivant;
		}
	}
	*/
	
	IteratorListe begin() {
		IteratorListe i(this->tete);
		return i;
	}

	IteratorListe end() {
		IteratorListe i(this->dernier);
		return i;
	}

	void operator +=(T &element) {
		if (this == NULL) throw InsertionException("operator +=> Unable to insert a new element");

		if (tete == NULL) { // premier element
			element.suivant = NULL;
			this->tete = &element;
			this->dernier = this->tete;
		} else {
			this->dernier->suivant = &element;
			this->dernier = &element;
			element.suivant = NULL;
		}
	}

	T *operator [](int index) {
		if (this == NULL) throw AccessException("operator []> Unable to access the list");
		if (index < 0) throw AccessException("operator []> No negative index");

		T *courant = this->tete;
		int i = 0;
		bool found = false;
		T *result = NULL;
		while ((courant != NULL) && (found == false)) {
			if (i == index) {
				found = true;
				result = courant;
			}
			courant = courant->suivant;
			i++;
		}
		if (found == false) throw AccessException("operator []> Index out of range");
		return result;
	}

	template <class Y> friend ostream & ::operator <<(ostream &os, const Liste<Y > &liste);
	
};

template <class Y> ostream& operator <<(ostream &os, const Liste<Y > &liste) {
	Y *courant = liste.tete;
	while (courant != NULL) {
		os << courant->rapport() << endl;
		courant = courant->suivant;
	}
	return os;
}

#endif
